{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Python 语法基础， IPython 解释器 和 Jupyter lab & Notebooks\n",
    "\n",
    "#  没有必要为了数据分析而去精通 Python\n",
    "# 鼓励使用 IPython shell 和 Jupyter 试验示例代码， 并学习不同类型、 函数和方法的文档\n",
    "\n",
    "# 基于表格的分析和处理大规模数据集的数据准备工具\n",
    "#  为了使用这些工具， 必须首先将混乱的数据规整为整洁的表格（或结构化） 形式\n",
    "\n",
    "# Python 是一个理想的语言， 可以快速整理数据\n",
    "# Python 使用得越熟练， 越容易准备新数据集以进行分析\n",
    "\n",
    "# 最好在 IPython 和 Jupyter 中亲自尝试使用的工具\n",
    "# 学会了如何启动 Ipython 和 Jupyter，与任何键盘驱动的操作环境一样， 记住常见的命令也是学习曲线的一部分\n",
    "\n",
    "# Python 的某些概念， 如类和面向对象编程，可能会发现在 Python 数据分析中很有用\n",
    "# 为了加强 Python 知识，建议你学习官方 Python 教程，https://docs.python.org/3/\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Python 解释器\n",
    "\n",
    "# Python 是解释性语言\n",
    "# Python 解释器同一时间只能运行一个程序的一条语句\n",
    "\n",
    "# 标准的交互 Python 解释器可以在命令行中通过键入  python 命令打开\n",
    "\n",
    "# $ python\n",
    "# >>> 提示输入代码\n",
    "# 要退出 Python 解释器返回终端， 可以输入 exit() 或按 Ctrl-D。\n",
    "\n",
    "# 运行 Python 程序只需调用 Python 的同时， 使用一个 .py 文件作为它的第一个参数\n",
    "# $ python hello_world.py\n",
    "\n",
    "# 一些 Python 程序员总是这样执行 Python 代码的\n",
    "# 从事数据分析和科学计算的人却会使用 IPython 解释器， 一个强化的 Python 解释器\n",
    "#  或 Jupyter notebooks， 一个网页代码笔记本，它原先是 IPython 的一个子项目\n",
    "\n",
    "# 用 %run 命令， IPython会同样执行指定文件中的代码，结束之后， 还可以与结果交互：\n",
    "# $ ipython\n",
    "# In [1]: %run hello_world.py\n",
    "# IPython 默认采用序号的格式 In [2]:\n",
    "# 与标准的 >>> 提示符不同"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# IPython基础\n",
    "\n",
    "# 运行 IPython Shell\n",
    "# 可以用 ipython 在命令行打开 IPython Shell， 就像打开普通的 Python 解释器：\n",
    "# $ ipython\n",
    "\n",
    "# 可以通过输入代码并按 Return（或 Enter ），运行任意 Python 语句\n",
    "#  当你只输入一个变量，它会显示代表的对象\n",
    "\n",
    "# 许多 Python 对象被格式化为更易读的形式， 或称作 pretty-printed ，它与普通的 print 不同\n",
    "# IPython 还支持执行任意代码块（通过一个华丽的复制-粘贴方法） 和整段 Python 脚本的功能\n",
    "\n",
    "# 运行 Jupyter Notebook\n",
    "# notebook 是 Jupyter 项目的重要组件之一， 它是一个代码、 文本（有标记或无标记） 、 数据可视化或其它输出的交互式文档\n",
    "#  Jupyter Notebook 需要与内核互动， 内核是 Jupyter 与其它编程语言的交互编程协议\n",
    "#  Python 的 Jupyter 内核是使用 IPython\n",
    "\n",
    "# 要启动 Jupyter， 在命令行中输入 jupyter notebook & jupyter lab\n",
    "# $ jupyter notebook\n",
    "# $ jupyter lab\n",
    "\n",
    "# 在多数平台上， Jupyter 会自动打开默认的浏览器（除非指定了 --no-browser ,只是作为一个服务器使用）\n",
    "# 或者，可以在启动 notebook 之后，手动打开网页 http://localhost:8888/\n",
    "\n",
    "# 笔记： 许多人使用 Jupyter 作为本地的计算环境， 但它也可以部署到服务器上远程访问\n",
    "\n",
    "# 输入一行 Python 代码。 然后按 Shift-Enter 执行\n",
    "\n",
    "# 当保存 notebook 时（File目录下的 Save and Checkpoint）， 会创建一个后缀名为 .ipynb 的文件\n",
    "#  这是一个自包含文件格式， 包含当前笔记本中的所有内容（包括所有已评估的代码输出）\n",
    "# 可以被其它 Jupyter 用户加载和编辑\n",
    "\n",
    "# 要加载存在的 notebook， 把它放到启动 notebook 进程的相同目录内\n",
    "# 虽然 Jupyter notebook 和 IPython shell 使用起来不同， 不过几乎所有的命令和工具都可以通用"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Tab补全\n",
    "\n",
    "# 从外观上， IPython shell 和标准的 Python 解释器只是看起来不同\n",
    "#  IPython shell 的进步之一是具备其它 IDE 和 交互计算分析环境都有的 tab 补全功能\n",
    "# 在 shell 中输入表达式， 按下 Tab， 会搜索已输入变量（对象、 函数等等）的命名空间\n",
    "# 也可以补全任何对象的方法和属性\n",
    "# 同样也适用于模块：\n",
    "\n",
    "# 在 Jupyter notebook 和新版的 IPython（5.0及以上）， 自动补全功能是下拉框的形式\n",
    "\n",
    "# 笔记： 注意， 默认情况下， IPython 会隐藏下划线开头的方法和属性，\n",
    "#    比如魔术方法和内部的 “私有” 方法和属性， 以避免混乱的显示这些也可以 tab 补全， 但是必须首先键入一个下划线才能看到它们\n",
    "# 如果喜欢总是在 tab 补全中看到这样的方法， 可以 IPython 配置中进行设置\n",
    "#  可以在 IPython 文档中查找方法\n",
    "\n",
    "# 除了补全命名、 对象和模块属性， Tab 还可以补全其它的\n",
    "#  当输入看似文件路径时（即使是 Python 字符串），按下 Tab 也可以补全电脑上对应的文件信息：\n",
    "# 结合 %run ， tab 补全可以节省许多键盘操作\n",
    "# 另外， tab 补全可以补全函数的关键词参数（包括等于号=）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 自省\n",
    "\n",
    "# 在变量前后使用问号 ？ ，可以显示对象的信息\n",
    "# 这可以作为对象的自省\n",
    "# 如果对象是一个函数或实例方法， 定义过的文档字符串， 也会显示出信息\n",
    "\n",
    "# ? 还有一个用途， 就是像 Unix 或 Windows 命令行一样搜索 IPython 的命名空间\n",
    "#  字符与通配符结合可以匹配所有的名字\n",
    "\n",
    "# 使用 ?? 会显示函数的源码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "# %run命令\n",
    "\n",
    "# 可以用 %run 命令运行所有的 Python 程序\n",
    "# 假设有一个文件 ipython_script_test.py\n",
    "# In [14]: %run ipython_script_test.py\n",
    "# 如果一个 Python 脚本需要命令行参数（在 sys.argv 中查找），\n",
    "#    可以在文件路径之后传递， 就像在命令行上运行一样\n",
    "\n",
    "# 笔记： 如果想让一个脚本访问 IPython 已经定义过的变量， 可以使用 %run -i\n",
    "#     在 Jupyter notebook 中，也可以使用  %load，它将脚本导入到一个代码格中\n",
    "# >>> %load ipython_script_test.py\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 中断运行的代码\n",
    "\n",
    "# 代码运行时按 Ctrl-C， 无论是 %run 或 长时间运行命令，都会导致 KeyboardInterrupt\n",
    "#  这会导致几乎所有 Python 程序立即停止， 除非一些特殊情况\n",
    "\n",
    "# 警告： 当 Python 代码调用了一些编译的扩展模块， 按 Ctrl-C 不一定将执行的程序立即停止\n",
    "# 在这种情况下，必须等待， 直到控制返回 Python 解释器， 或者在更糟糕的情况下强制终止  Python 进程\n",
    "\n",
    "\n",
    "# 从剪贴板执行程序\n",
    "# 如果使用 Jupyter notebook，可以将代码复制粘贴到任意代码格执行\n",
    "#  在 IPython shell中也可以从剪贴板执行\n",
    "# 最简单的方法是使用 %paste 和 %cpaste 函数\n",
    "\n",
    "#  %paste 可以直接运行剪贴板中的代码：\n",
    "# In [17]: %paste\n",
    "\n",
    "# %cpaste 功能类似， 但会给出一条提示：\n",
    "# In [18]: %cpaste\n",
    "\n",
    "# 使用 %cpaste ，你可以粘贴任意多的代码再运行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 键盘快捷键\n",
    "# IPython 有许多键盘快捷键进行导航提示（类似 Emacs 文本编辑器或 UNIX bash Shell） 和交互 shell 的历史命令\n",
    "\n",
    "# Jupyter notebooks 有另外一套庞大的快捷键\n",
    "#  建议 参阅  Jupyter notebook 的帮助文档\n",
    "\n",
    "# 魔术命令\n",
    "\n",
    "# IPython 中特殊的命令（Python中没有） 被称作“魔术”命令\n",
    "# 这些命令可以使普通任务更便捷， 更容易控制 IPython 系统\n",
    "# 魔术命令是在指令前添加百分号 % 前缀\n",
    "\n",
    "# 例如， 可以用 %timeit （这个命令后面会详谈） 测量任何 Python 语句，例如 矩阵乘法 的执行时间\n",
    "\n",
    "# 魔术命令可以被看做 IPython 中运行的命令行\n",
    "# 许多魔术命令有 “命令行” 选项， 可以通过 ？ 查看\n",
    "# In [21]: %debug?\n",
    "\n",
    "# 魔术函数默认可以不用百分号， 只要没有变量和函数名相同\n",
    "#  这个特点被称为 “自动魔术”，可以用 %automagic 打开或关闭\n",
    "\n",
    "# 一些魔术函数与 Python 函数很像， 它的结果可以赋值给一个变量\n",
    "# In [22]: %pwd\n",
    "# Out[22]: '/home/wesm/code/pydata-book\n",
    "# In [23]: foo = %pwd\n",
    "    \n",
    "# IPython 的文档可以在 shell 中打开\n",
    "# 建议用 %quickref 或 %magic 学习下所有特殊命令\n",
    "\n",
    "# 表 2-2 列出了一些可以提高生产率的交互计算和 Python 开发的 IPython 指令集"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![self](./../image/magic.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 集成 Matplotlib\n",
    "\n",
    "# IPython 在分析计算领域能够流行的原因之一是它非常好的集成了数据可视化和其它用户界面库\n",
    "# 比如 matplotlib, %matplotlib 魔术函数配置了 IPython shell 和 Jupyter notebook 中的 matplotlib\n",
    "\n",
    "# 这点很重要， 其它创建的图不会出现（ notebook ） 或获取 session 的控制， 直到结束（shell）\n",
    "# 在 IPython shell 中， 运行 %matplotlib 可以进行设置， 可以创建多个绘图窗口， 而不会干扰控制台 session：\n",
    "# In [26]: %matplotlib\n",
    "\n",
    "# 在 JUpyter中， 命令有所不同\n",
    "# In [26]: %matplotlib inline\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Python语法基础\n",
    "# 概述基本的 Python 概念和语言机制\n",
    "# 详细介绍 Python 的数据结构、 函数和其它内建工具\n",
    "\n",
    "# 语言的语义\n",
    "# Python 的语言设计强调的是可读性、 简洁和清晰\n",
    "#  有些人称 Python 为 “ 可执行的伪代码 ”\n",
    "\n",
    "# 使用缩进， 而不是括号\n",
    "# Python 使用空白字符（tab和空格） 来组织代码， 而不是像其它语言， 比如 R、 C++、 JAVA 和 Perl 那样使用括号\n",
    "# 冒号标志着缩进代码块的开始， 冒号之后的所有代码的缩进量必须相同， 直到代码块结束\n",
    "#  不管是否喜欢这种形式， 使用空白符是 Python 程序员开发的一部分，这可以让 python 的代码可读性大大优于其它语言\n",
    "\n",
    "# 笔记： 强烈建议你使用 四个空格 作为默认的缩进， 可以使用 tab 代替四个空格\n",
    "#     许多文本编辑器的设置是使用制表位替代空格\n",
    "# 某些人使用 tabs 或 不同数目的空格数， 常见的是使用两个空格\n",
    "# 大多数情况下， 四个空格 是大多数人采用的方法， 因此建议\n",
    "\n",
    "# 应该已经看到， Python 的语句不需要用分号结尾\n",
    "#    但是， 分号却可以用来给同在一行的语句切分\n",
    "# Python 不建议将多条语句放到一行， 这会降低代码的可读性\n",
    "\n",
    "\n",
    "# 万物皆对象 ( 如同 Unix 和 Linux 一致，万物皆文件 )\n",
    "# Python 语言的一个重要特性就是它的对象模型的一致性\n",
    "#  每个数字、 字符串、 数据结构、 函数、类、 模块等等， 都是在 Python 解释器的自有 “盒子” 内， 它被认为是 Python 对象\n",
    "#  每个对象都有类型（例如， 字符串或函数） 和 内部数据\n",
    "# 在实际中， 这可以让语言非常灵活， 因为函数也可以被当做对象使用。\n",
    "\n",
    "# 注释\n",
    "# 任何前面带有井号 # 的文本都会被 Python 解释器忽略\n",
    "#  这通常被用来添加注释\n",
    "# 有时， 想排除一段代码， 但并不删除， 简便的方法就是将其注释掉：\n",
    "# 也可以在执行过的代码后面添加注释\n",
    "# 一些人习惯在代码之前添加注释\n",
    "\n",
    "# 函数和对象方法调用\n",
    "# 可以用 圆括号 调用函数， 传递零个或几个参数， 或者将返回值给一个变量\n",
    "# 几乎 Python 中的每个对象都有 附加的函数， 称作方法， 可以用来访问对象的内容\n",
    "#    可以用下面的语句调用：\n",
    "# obj.some_method(x, y, z)\n",
    "\n",
    "# 函数可以使用 位置 和 关键词参数\n",
    "# result = f(a, b, c, d=5, e='foo')\n",
    "\n",
    "# 变量和参数传递\n",
    "# 当在 Python 中创建变量（或名字），就在等号右边创建了一个对这个变量的引用\n",
    "# 理解 Python 的引用的含义， 数据是何时、 如何、 为何复制的， 是非常重要的\n",
    "# 尤其是用 Python 处理大的数据集时\n",
    "\n",
    "# 笔记： 赋值也被称作绑定，是把一个名字绑定给一个对象\n",
    "#     变量名有时可能被称为绑定变量\n",
    "\n",
    "# 当将对象作为参数传递给函数时， 新的局域变量创建了对原始对象的引用， 而不是复制\n",
    "#  如果在函数里绑定一个新对象到一个变量， 这个变动不会反映到上一层， 因此可以改变可变参数的内容\n",
    "\n",
    "# 动态引用， 强类型\n",
    "# 与许多编译语言（如 JAVA 和 C++） 对比， Python中的对象引用不包含附属的类型\n",
    "# 变量是在特殊命名空间中的对象的名字， 类型信息保存在对象自身中\n",
    "\n",
    "# 一些人可能会说 Python 不是 “类型化语言”， 这是不正确的\n",
    "# Python 被认为是强类型化语言， 意味着每个对象都有明确的类型（或类），默许转换只会发生在特定的情况下\n",
    "# 知道对象的类型很重要， 最好能让函数可以处理多种类型的输入\n",
    "#    可以用 isinstance 函数检查对象是某个类型的实例：\n",
    "# In [21]: a = 5\n",
    "# In [22]: isinstance(a, int)\n",
    "# Out[22]: True\n",
    "\n",
    "# isinstance 可以用类型元组， 检查对象的类型是否在元组中：\n",
    "# In [23]: a = 5; b = 4.5\n",
    "# In [24]: isinstance(a, (int, float))\n",
    "# Out[24]: True\n",
    "# In [25]: isinstance(b, (int, float))\n",
    "# Out[25]: True\n",
    "\n",
    "\n",
    "# 属性和方法\n",
    "# Python 的对象通常都有属性（其它存储在对象内部的 Python 对象） 和方法（对象的附属函数可以访问对象的内部数据）\n",
    "#      可以用 obj.attribute_name 访问属性和方法\n",
    "\n",
    "# 在其它语言中， 访问对象的名字通常称作 “反射”\n",
    "\n",
    "# 鸭子类型\n",
    "# 经常地，可能不关心对象的类型， 只关心对象是否有某些方法或用途\n",
    "#   这通常被称为 “鸭子类型”， 来自 “走起来像鸭子、 叫起来像鸭子， 那么它就是鸭子”的说法\n",
    "\n",
    "# 可以通过验证一个对象是否遵循迭代协议， 判断它是可迭代的\n",
    "# 对于许多对象， 这意味着它有一个 __iter__ 魔术方法， 其它更好的判断方法是使用 iter 函数：\n",
    "# def isiterable(obj):\n",
    "#     try:\n",
    "#         iter(obj)\n",
    "#         return True\n",
    "#     except TypeError: # not iterable\n",
    "#         return False\n",
    "\n",
    "# 这个函数会返回字符串以及大多数 Python 集合类型为 True\n",
    "\n",
    "# 总是用这个功能编写可以接受多种输入类型的函数\n",
    "#  常见的例子是编写一个函数可以接受任意类型的序列（list、 tuple、 ndarray） 或是迭代器\n",
    "#  可先检验对象是否是列表（或是 NUmPy 数组），如果不是的话，将其转变成列表\n",
    "\n",
    "# 引入\n",
    "# 在 Python中， 模块就是一个有 .py 扩展名、 包含 Python 代码的文件\n",
    "# 如果想从同目录下的另一个文件访问 some_module.py 中定义的变量和函数：\n",
    "# import some_module\n",
    "# from some_module import f, g, PI\n",
    "# 使用 as 关键词， 你可以给引入起不同的变量名：\n",
    "# import some_module as sm\n",
    "# from some_module import PI as pi, g as gf\n",
    "\n",
    "# 二元运算符和比较运算符\n",
    "# 大多数二元数学运算和比较都不难想到：\n",
    "# 要判断两个引用是否指向同一个对象， 可以使用 is 方法。 is not 可以判断两个对象是不同的：\n",
    "# # is 和 is not 常用来判断一个变量是否为 None ， 因为只有一个 None 的实例：\n",
    "\n",
    "# 可变与不可变对象\n",
    "# Python中的大多数对象， 比如列表、 字典、 NumPy数组， 和用户定义的类型（类） 都是可变的, 意味着这些对象或包含的值可以被修改：\n",
    "# 其它的， 例如 字符串 和 元组， 是不可变的：\n",
    "\n",
    "# 记住， 可以修改一个对象并不意味就要修改它\n",
    "#      这被称为副作用\n",
    "# 例如， 当写一个函数， 任何副作用都要在文档或注释中写明\n",
    "#     如果可能的话， 推荐避免副作用， 采用不可变的方式， 即使要用到可变对象\n",
    "\n",
    "\n",
    "# 标量类型\n",
    "# Python 的标准库中有一些内建的类型， 用于处理数值数据、 字符串、 布尔值， 和日期时间\n",
    "#  这些单值类型被称为 标量类型， 称其为标量\n",
    "\n",
    "# 数值类型\n",
    "# Python 的主要数值类型是 int 和 float\n",
    "# int 可以存储任意大的数：\n",
    "# 浮点数使用 Python的 float 类型\n",
    "# 每个数都是双精度（64位） 的值\n",
    "# 也可以用科学计数法表示：\n",
    "# 不能得到整数的除法会得到浮点数：\n",
    "# 要获得C-风格的整除（去掉小数部分） ， 可以使用底除运算符//：\n",
    "\n",
    "# 字符串\n",
    "# 许多人是因为 Python 强大而灵活的字符串处理而使用 Python 的\n",
    "# 可以用 单引号 或 双引号 来写字符串\n",
    "# 对于有换行符的字符串， 可以使用三引号， ''' 或 \"\"\" 都行\n",
    "# 字符串 c 实际包含四行文本， \"\"\" 后面和 lines 后面的换行符\n",
    "#  可以用 count 方法计算 c 中的新的行\n",
    "# Python的字符串是不可变的， 不能修改字符串\n",
    "# 许多 Python 对象使用 str 函数可以被转化为字符串：\n",
    "# 字符串是一个序列的 Unicode字符， 因此可以像其它序列， 比如列表和元组一样处理\n",
    "\n",
    "# 语法 s[:3] 被称作切片， 适用于许多 Python 序列\n",
    "# 反斜杠是转义字符， 意思是它备用来表示特殊字符， 比如换行符 \\n 或 Unicode字符\n",
    "#   要写一个包含 反斜杠 的字符串， 需要进行转义\n",
    "# 如果字符串中包含许多反斜杠， 但没有特殊字符， 这样做就很麻烦\n",
    "# 幸好， 可以在字符串前面加一个 r， 表明字符就是它自身, 称之为原生字符串\n",
    "#  r表示raw。\n",
    "\n",
    "# 将两个字符串合并， 会产生一个新的字符串：\n",
    "# 字符串的模板化或格式化， 是另一个重要的主题\n",
    "#   Python 3 拓展了此类的方法，字符串对象有 format 方法， 可以替换格式化的参数为字符串， 产生一个新的字符串\n",
    "# 要替换参数为这些格式化的参数， 传递 format 方法一个序列\n",
    "# 字符串格式化是一个很深的主题， 有多种方法和大量的选项， 可以控制字符串中的值是如何格式化的。 推荐参阅Python官方文档。\n",
    "\n",
    "# 字节和 Unicode\n",
    "# 在 Python 3及以上版本中， Unicode是一级的字符串类型， 这样可以更一致的处理 ASCII 和 Non ASCII 文本\n",
    "#    在老的 Python 版本中， 字符串都是字节， 不使用 Unicode 编码\n",
    "# 假如知道字符编码，可以将其转化为 Unicode\n",
    "# 可以用 encode 将这个 Unicode 字符串编码为 UTF-8\n",
    "# 如果知道一个字节对象的 Unicode 编码， 用 decode 方法可以解码\n",
    "# 虽然 UTF-8 编码已经变成主流， 但因为历史的原因， 仍然可能碰到其它编码的数据\n",
    "# In [82]: val.encode('latin1')\n",
    "# In [83]: val.encode('utf-16')\n",
    "# In [84]: val.encode('utf-16le')\n",
    "\n",
    "# 工作中碰到的文件很多都是字节对象， 盲目地将所有数据编码为 Unicode 是不可取的\n",
    "# 虽然用的不多， 可以在字节文本的前面加上一个 b\n",
    "\n",
    "# 布尔值\n",
    "# Python 中的布尔值有两个， True 和 False\n",
    "# 比较和其它条件表达式可以用 True 和 False 判断\n",
    "# 布尔值可以与 and 和 or 结合使用\n",
    "\n",
    "# 类型转换\n",
    "# str、 bool、 int 和 float 也是函数， 可以用来转换类型\n",
    "\n",
    "# None\n",
    "# None 是 Python 的空值类型\n",
    "# 如果一个函数没有明确的返回值， 就会默认返回 None\n",
    "# None 也常常作为函数的默认参数\n",
    "# 另外， None 不仅是一个保留字， 还是唯一的 NoneType 的实例\n",
    "\n",
    "# 日期和时间\n",
    "# Python 内建的 datetime 模块提供了 datetime 、 date 和 time 类型\n",
    "#      datetime 类型结合了 date 和 time ， 是最常使用的\n",
    "# 根据 datetime 实例，可以用 date 和 time 提取出各自的对象\n",
    "\n",
    "# strftime 方法可以将 datetime 格式化为字符串\n",
    "# strptime 可以将字符串转换成 datetime 对象\n",
    "\n",
    "# 聚类或对时间序列进行分组， 替换 datetimes 的 time 字段有时会很有用\n",
    "# 例如， 用 0 替换分和秒\n",
    "# 因为 datetime.datetime 是不可变类型， 会产生新的对象\n",
    "# 两个 datetime 对象的差会产生一个 datetime.timedelta 类型\n",
    "\n",
    "# 控制流\n",
    "# Python 有若干内建的关键字进行条件逻辑、 循环和其它控制流操作\n",
    "\n",
    "# if、 elif 和 else\n",
    "# if是最广为人知的控制流语句。 它检查一个条件， 如果为True， 就执行后面的语句\n",
    "# if 后面可以跟一个或多个 elif ， 所有条件都是False时， 还可以添加一个 else ：\n",
    "# 如果某个条件为True， 后面的 elif 就不会被执行。 当使用and和or时， 复合条件语句是从左到右执行\n",
    "\n",
    "# for 循环\n",
    "# for 循环是在一个集合（列表或元组） 中进行迭代， 或者就是一个迭代器\n",
    "# for 循环的标准语法是：\n",
    "# for value in collection:\n",
    "#     do something with value\n",
    "\n",
    "# 可以用 continue 使 for 循环提前， 跳过剩下的部分\n",
    "# 可以用 break 跳出 for 循环\n",
    "# break 只中断 for 循环的最内层， 其余的 for 循环仍会运行\n",
    "# 如果集合或迭代器中的元素序列（元组或列表） ， 可以用for循环将其方便地拆分成变量：\n",
    "\n",
    "# While循环\n",
    "# while循环指定了条件和代码， 当条件为False或用break退出循环， 代码才会退出：\n",
    "\n",
    "# pass\n",
    "# pass 是 Python中的非操作语句。 代码块不需要任何动作时可以使用（作为未执行代码的占位符）\n",
    "#  因为 Python需要使用空白字符划定代码块， 所以需要pass：\n",
    "\n",
    "# range\n",
    "# range函数返回一个迭代器， 它产生一个均匀分布的整数序列：\n",
    "# range的三个参数是（起点， 终点， 步进） ：\n",
    "# 可以看到， range产生的整数不包括终点。 range的常见用法是用序号迭代序列：\n",
    "# 可以使用 list 来存储 range 在其他数据结构中生成的所有整数， 默认的迭代器形式通常是你想要的。\n",
    "# 虽然 range 可以产生任意大的数， 但任意时刻耗用的内存却很小\n",
    "\n",
    "# 三元表达式\n",
    "# Python 中的三元表达式可以将 if-else 语句放到一行里\n",
    "#     语法如下：\n",
    "# value = true-expr if condition else false-expr\n",
    "# true-expr 或 false-expr 可以是任何Python代码\n",
    "\n",
    "# 它和下面的代码效果相同：\n",
    "# if condition:\n",
    "#     value = true-expr\n",
    "# else:\n",
    "#     value = false-expr\n",
    "\n",
    "# 和 if-else 一样， 只有一个表达式会被执行\n",
    "# 因此， 三元表达式中的 if 和 else 可以包含大量的计算， 但只有 True 的分支会被执行\n",
    "# 虽然使用三元表达式可以压缩代码， 但会降低代码可读性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 学习了 IPython shell 和 Jupyter notebook 的基础\n",
    "# 探索 IPython 更深层次的功能， 可以从控制台 或在 jupyter使用\n",
    "\n",
    "\n",
    "# B.1 使用命令历史\n",
    "# Ipython 维护了一个位于磁盘的小型数据库， 用于保存执行的每条指令\n",
    "#  它的用途有：\n",
    "#     1、只用最少的输入， 就能搜索、 补全和执行先前运行过的指令\n",
    "#     2、在不同 session 间保存命令历史\n",
    "#     3、 将日志输入/输出历史到一个文件\n",
    "\n",
    "# 这些功能在 shell 中， 要比 notebook 更为有用， 因为 notebook 从设计上是将输入和输出的代码放到每个代码格子中\n",
    "\n",
    "# 搜索和重复使用命令历史\n",
    "# Ipython 可以搜索和执行之前的代码或其他命令\n",
    "#   这个功能非常有用， 因为可能需要重复执行同样的命令， 例如 %run 命令，或其它代码\n",
    "# 输入 %run 命令， 然后按 Ctrl + P 或 上箭头\n",
    "# 这样就可以搜索历史命令， 匹配输入字符的命令\n",
    "# 多次按 Ctrl + P 或 上箭头， 会继续搜索命令\n",
    "# 可以按下 Ctrl + N 或 下箭头， 向前移动历史命令\n",
    "\n",
    "# Ctrl-R 可以带来如同 Unix 风格 shell（比如 bash shell） 的 readline 的部分增量搜索功能\n",
    "# 在 Windows 上， readline 功能是被 IPython 模仿的\n",
    "#  要使用这个功能， 先按 Ctrl-R， 然后输入一些包含于输入行的想要搜索的字符\n",
    "\n",
    "# Ctrl-R 会循环历史， 找到匹配字符的每一行\n",
    "\n",
    "\n",
    "# 输入和输出变量\n",
    "# 忘记将函数调用的结果分配给变量是非常烦人的\n",
    "#  IPython 的一个 session 会在一个特殊变量， 存储输入和输出 Python 对象的引用\n",
    "#  前面两个输出会分别存储在 （一个下划线） 和 _（两个下划线） 变量\n",
    "\n",
    "# 输入变量是存储在名字类似 _iX 的变量中， X是输入行的编号\n",
    "#  对于每个输入变量， 都有一个对应的输出变量 _X\n",
    "# 因为输入变量是字符串， 它们可以用 Python 的 exec 关键字再次执行\n",
    "\n",
    "# 有几个魔术函数可以利用输入和输出历史\n",
    "#    %hist 可以打印所有或部分的输入历史， 加上或不加上编号\n",
    "#    %reset 可以清理交互命名空间， 或输入和输出缓存\n",
    "#    %xdel 魔术函数可以去除 IPython 中对一个特别对象的所有引用\n",
    "\n",
    "# 对于关于这些魔术方法的更多内容， 请查看文档\n",
    "# 警告： 当处理非常大的数据集时， 要记住 IPython 的输入和输出的历史会造成被引用的对象不被垃圾回收（释放内存），\n",
    "#     即使你使用 del 关键字从交互命名空间删除变量\n",
    "# 在这种情况下， 小心使用 xdel % 和 %reset 可以帮助你避免陷入内存问题"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "# B.2 与操作系统交互\n",
    "# IPython 的另一个功能是无缝连接文件系统和操作系统\n",
    "# 这意味着， 在同时做其它事时， 无需退出 IPython， \n",
    "#   可以像 Windows 或 Unix 使用命令行操作，包括 shell 命令、更改目录、用 Python 对象（列表或字符串） 存储结果\n",
    "# 它还有简单的命令别名和目录书签功能\n",
    "\n",
    "# 表 B-1 总结了调用 shell 命令的魔术函数和语法\n",
    "\n",
    "# Shell 命令和别名\n",
    "# 用叹号开始一行， 是告诉 IPython 执行叹号后面的所有内容\n",
    "#    这意味着可以删除文件（取决于操作系统， 用 rm 或 del）、改变目录或执行任何其他命令\n",
    "\n",
    "# 通过给变量加上叹号， 可以在一个变量中存储命令的控制台输出\n",
    "# 当使用！ ， IPython 还可以替换定义在当前环境的 Python 值\n",
    "# 要这么做， 可以在变量名前面加上  $ 符号\n",
    "\n",
    "# %alias 魔术函数可以自定义 shell 命令的快捷方式\n",
    "\n",
    "# 可以执行多个命令， 就像在命令行中一样， 只需用分号隔开：\n",
    "\n",
    "# 当 session 结束，定义的别名就会失效\n",
    "# 要创建恒久的别名， 需要使用配置\n",
    "\n",
    "\n",
    "# 目录书签系统\n",
    "# IPython 有一个简单的目录书签系统， 可以让你保存常用目录的别名， 这样在跳来跳去的时候会非常方便\n",
    "\n",
    "# 这么做之后， 当使用 %cd 魔术命令， 就可以使用定义的书签\n",
    "# 如果书签的名字， 与当前工作目录的一个目录重名，可以使用 -b 标志来覆写， 使用书签的位置\n",
    "# 使用 %bookmark 的 -l 选项， 可以列出所有的书签\n",
    "\n",
    "# 书签， 和别名不同， 在 session 之间是保持的"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![self](./../image/osshell.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "# B.3 软件开发工具\n",
    "# 除了作为优秀的交互式计算和数据探索环境， IPython 也是有效的 Python 软件开发工具\n",
    "#  在数据分析中， 最重要的是要有正确的代码\n",
    "\n",
    "# 幸运的是， IPython 紧密集成了和加强了 Python 内置的 pdb 调试器\n",
    "# 第二， 需要快速的代码。 对于这点， IPython 有易于使用的代码计时和分析工具\n",
    "\n",
    "# 交互调试器\n",
    "# IPython 的调试器用 tab 补全、 语法增强、 逐行异常追踪增强了 pdb\n",
    "#   调试代码的最佳时间就是刚刚发生错误\n",
    "#   异常发生之后就输入 %debug，就启动了调试器， 进入抛出异常的堆栈框架\n",
    "\n",
    "# 一旦进入调试器， 就可以执行任意的 Python 代码， 在每个堆栈框架中检查所有的对象和数据（解释器会保持它们活跃）\n",
    "#   默认是从错误发生的最低级开始 通过 u（up） 和 d（down），可以在不同等级的堆栈踪迹切换\n",
    "\n",
    "# 执行 %pdb 命令， 可以在发生任何异常时让 IPython 自动启动调试器， \n",
    "#   许多用户会发现这个功能非常好用\n",
    "\n",
    "# 用调试器帮助开发代码也很容易， 特别是当希望设置断点或在函数和脚本间移动， 以检查每个阶段的状态\n",
    "#    多种方法可以实现。 第一种是使用 %run 和 -d， 它会在执行传入脚本的任何代码之前调用调试器。 你必须马上按 s（step） 以进入脚本：\n",
    "# 然后， 就可以决定如何工作。\n",
    "# 例如， 在前面的异常，可以设置一个断点， 就在调用之前， 然后运行脚本， 在遇到断点时按 c（continue）\n",
    "# 这时， 可以 step 进入 ， 或通过按 n（next） 执行， 进入下一行：\n",
    "# 然后，可以进入 throws_an_exception， 到达发生错误的一行， 查看变量\n",
    "# 注意， 调试器的命令是在变量名之前， 在变量名前面加叹号！ 可以查看内容\n",
    "\n",
    "# 提高使用交互式调试器的熟练度需要练习和经验\n",
    "# 表 B-2， 列出了所有调试器命令\n",
    "\n",
    "# 习惯了 IDE，可能觉得终端的调试器在一开始会不顺手， 但会觉得越来越好用\n",
    "#   一些 Python 的 IDEs 有很好的 GUI调试器， 选择顺手的就好。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![self](./../image/debug.png)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用调试器的其它方式\n",
    "# 还有一些其它工作可以用到调试器\n",
    "\n",
    "#  第一个是使用特殊的 set_trace 函数（根据 pdb.set_trace 命名的），这是一个简装的断点\n",
    "# 第一个函数 set_trace 非常简单。 如果想暂时停下来进行仔细检查（比如发生异常之前），可以在代码的任何位置使用 set_trace\n",
    "\n",
    "# 刚看的 debug 函数， 可以让方便的在调用任何函数时使用调试器\n",
    "\n",
    "# 代码计时： %time 和 %timeit\n",
    "# 对于大型和长时间运行的数据分析应用， 可能希望测量不同组件或单独函数调用语句的执行时间\n",
    "# 可能想知道哪个函数占用的时间最长\n",
    "\n",
    "#  幸运的是， IPython 可以让你开发和测试代码时，很容易地获得这些信息\n",
    "# 手动用 time 模块和它的函数 time.clock 和 time.time 给代码计时，既单调又重复，因为必须要写一些无趣的模板化代码\n",
    "\n",
    "# 因为这是一个很普通的操作， IPython 有两个魔术函数，  %time 和 %timeit，可以自动化这个过程\n",
    "#    %time 会运行一次语句， 报告总共的执行时间\n",
    "# Wall time（wall-clock time的简写） 是主要关注的。但是这种测量方法并不准确\n",
    "\n",
    "# 如果用 %time 多次测量， 就会发现结果是变化的\n",
    "# 要想更准确， 可以使用 %timeit 魔术函数\n",
    "#  给出任意一条语句， 它能多次运行这条语句以得到一个更为准确的时间\n",
    "\n",
    "# %timeit 特别适合分析执行时间短的语句和函数， 即使是微秒或纳秒\n",
    "#  这些时间可能看起来毫不重要， 但是一个 20 微秒的函数执行 1百万 次就比一个 5 微秒的函数长15秒\n",
    "\n",
    "# 基础分析： %prun 和 %run -p\n",
    "# 分析代码与代码计时关系很紧密， 除了它关注的是 “时间花在了哪里”\n",
    "#  Python 主要的分析工具是 cProfile 模块， 它并不局限于 IPython\n",
    "# cProfile 会执行一个程序或任意的代码块，并会跟踪每个函数执行的时间\n",
    "\n",
    "# 使用 cProfile 的通常方式是在命令行中运行一整段程序， 输出每个函数的累积时间\n",
    "# 除了在命令行中使用， cProfile 也可以在程序中使用， 分析任意代码块， 而不必运行新进程\n",
    "\n",
    "# Ipython 的 %prun 和 %run -p， 有便捷的接口实现这个功能\n",
    "#   %prun 使用类似 cProfile 的命令行选项，但是可以分析任意 Python 语句，而不用整个 py文件\n",
    "# 相似的， 调用 %run -p -s cumulative cprof_example.py 有和命令行相似的作用， 只是不用离开 Ipython\n",
    "\n",
    "\n",
    "# 在 Jupyter notebook 中，可以使用 %%prun 魔术方法（两个%） 来分析一整段代码\n",
    "#   这会弹出一个带有分析输出的独立窗口\n",
    "# 便于快速回答一些问题， 比如 “为什么这段代码用了这么长时间” ？\n",
    "\n",
    "# 使用 IPython 或 Jupyter， 还有一些其它工具可以让分析工作更便于理解\n",
    "#   其中之一是  SnakeViz（https://github.com/jiffyclub/snakeviz/），它使用 d3.js 产生一个分析结果的交互可视化界面\n",
    "\n",
    "\n",
    "# 逐行分析函数\n",
    "# 有些情况下， 用 %prun（或其它基于cProfile的分析方法） 得到的信息， \n",
    "#    不能获得函数执行时间的整个过程， 或者结果过于复杂， 加上函数名， 很难进行解读\n",
    "# 对于这种情况， 有一个小库叫做 line_profiler（可以通过 PyPI或 包管理工具获得） \n",
    "\n",
    "# 它包含 IPython 插件， 可以启用一个新的魔术函数 %lprun，\n",
    "#    可以对一个函数或多个函数进行逐行分析。\n",
    "# 可以通过修改 IPython 配置（查看 IPython 文档配置） 加入下面这行， 启用这个插件：\n",
    "# A list of dotted module names of IPython extensions to load.\n",
    "# c.TerminalIPythonApp.extensions = ['line_profiler']\n",
    "\n",
    "# 还可以运行命令：\n",
    "# %load_ext line_profiler\n",
    "\n",
    "# line_profiler也可以在程序中使用（查看完整文档） ， 但是在IPython中使用是最为强大的\n",
    "\n",
    "# 经验是用 %prun (cProfile) 进行宏观分析， \n",
    "# %lprun (line_profiler) 做 微观分析\n",
    "\n",
    "# 笔记： 使用 %lprun 必须要指明函数名的原因是追踪每行的执行时间的损耗过多\n",
    "#     追踪无用的函数会显著地改变结果"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "# B.4 使用 IPython 高效开发的技巧\n",
    "# 方便快捷地写代码、 调试和使用是每个人的目标\n",
    "# 除了代码风格， 流程细节（比如代码重载） 也需要一些调整\n",
    "# 因此，更像是门艺术而不是科学， 还需要你不断的试验， 以达成高效\n",
    "# 最终， 要能结构优化代码， 并且能省时省力地检查程序或函数的结果\n",
    "\n",
    "# 发现用 IPython 设计的软件比起命令行， 要更适合工作\n",
    "#  尤其是当发生错误时， 需要检查自己或别人写的数月或数年前写的代码的错误\n",
    "\n",
    "\n",
    "# 重载模块依赖\n",
    "# 在 Python中， 当你输入 import some_lib， \n",
    "# some_lib 中的代码就会被执行， 所有的变量、 函数和定义的引入， 就会被存入到新创建的 some_lib 模块命名空间\n",
    "# 当下一次输入 some_lib， 就会得到一个已存在的模块命名空间的引用\n",
    "\n",
    "# 潜在的问题是当 %run 一个脚本， 它依赖于另一个模块，而这个模块做过修改，就会产生问题\n",
    "# 这是因为 Python 模块系统的 “一次加载” 机制\n",
    "#  这一点区分了 Python 和其它数据分析环境， 比如 MATLAB， 它会自动传播代码修改\n",
    "\n",
    "# 解决这个问题，有多种方法\n",
    "#   第一种是在标准库 importlib 模块中使用 reload 函数\n",
    "# importlib.reload(some_lib)\n",
    "\n",
    "# 这可以保证每次运行 .py 时可以加载最新的 some_lib.py\n",
    "#   很明显， 如果依赖更深， 在各处都使用 reload 是非常麻烦的\n",
    "\n",
    "# 对于这个问题， IPython 有一个特殊的 dreload 函数（它不是魔术函数） 重载深层的模块\n",
    "# 如果运行过 some_lib.py， 然后输入 dreload(some_lib)， 就会尝试重载 some_lib 和它的依赖\n",
    "# 不过， 这个方法不适用于所有场景， 但比重启 IPython 强多了\n",
    "\n",
    "# 代码设计技巧\n",
    "# 对于这个问题， 没有简单的对策， 但是有一些原则， 在工作中发现很好用的\n",
    "#   1、保持相关对象和数据活跃\n",
    "# 在 IPython 中运行这个程序会发生问题， 你发现是什么了吗？\n",
    "#  运行之后， 任何定义在 main 函数中的结果和对象都不能在 IPython 中被访问到\n",
    "# 更好的方法是将 main 中的代码直接在模块的命名空间中执行（或者在 __name__ == '__main__': 中， 如果你想让这个模块可以被引用） \n",
    "#  当 %rundiamante， 就可以查看所有定义在 main 中的变量\n",
    "\n",
    "# 这等价于在 Jupyter notebook 的代码格中定义一个顶级变量\n",
    "\n",
    "\n",
    "# 扁平优于嵌套\n",
    "# 深层嵌套的代码总联想到洋葱皮\n",
    "#  当测试或调试一个函数时，需要剥多少层洋葱皮才能到达目标代码呢\n",
    "\n",
    "# “ 扁平优于嵌套 ”是 Python 之禅的一部分， 它也适用于交互式代码开发\n",
    "#  尽量将函数和类去耦合和模块化， 有利于测试、 调试和交互式使用\n",
    "\n",
    "\n",
    "# 克服对大文件的恐惧\n",
    "# 如果之前是写 JAVA（或者其它类似的语言），可能被告知要让文件简短\n",
    "#  在多数语言中， 这都是合理的建议： 太长会让人感觉是坏代码， 意味着重构和重组是必要的\n",
    "\n",
    "# 但是， 在用 IPython 开发时， 运行 10 个相关联的小文件（小于100行） ，比起两个或三个长文件，会让你更头疼\n",
    "# 更少的文件意味着重载更少的模块和更少的编辑时在文件中跳转\n",
    "\n",
    "# 发现维护大模块， 每个模块都是紧密组织的， 会更实用和 Pythonic\n",
    "#  经过方案迭代， 有时会将大文件分解成小文件\n",
    "#   不建议 极端化 这条建议， 那样会形成一个单独的超大文件\n",
    "#  找到一个合理和直观的大型代码模块库和封装结构往往需要一点工作， 但这在团队工作中非常重要\n",
    "# 每个模块都应该结构紧密， 并且应该能直观地找到负责每个功能领域功能和类"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# B.5 IPython高级功能\n",
    "\n",
    "# 要全面地使用 IPython 系统需要用另一种稍微不同的方式写代码， 或深入 IPython 的配置\n",
    "\n",
    "# 让类是对 IPython 友好的\n",
    "# IPython 会尽可能地在控制台美化展示每个字符串\n",
    "#  对于许多对象， 比如字典、 列表和元组， 内置的 pprint 模块可以用来美化格式\n",
    "# 但是， 在用户定义的类中， 必自己生成字符串\n",
    "\n",
    "\n",
    "# 文件和配置\n",
    "# 通过扩展配置系统， 大多数 IPython 和 Jupyter notebook 的外观（颜色、 提示符、 行间距等等）和动作都是可以配置的\n",
    "# 通过配置， 可以做到：\n",
    "#     1、改变颜色主题\n",
    "#     2、改变输入和输出提示符， 或删除输出之后、 输入之前的空行\n",
    "#     3、执行任意Python语句（例如， 引入总是要使用的代码或者每次加载IPython都要运行的内容）\n",
    "#     4、启用IPython总是要运行的插件， 比如line_profiler中的%lprun魔术函数\n",
    "#     5、启用Jupyter插件\n",
    "#     6、定义自己的魔术函数或系统别名\n",
    "\n",
    "# IPython 的配置存储在特殊的 ipython_config.py 文件中， 它通常是在用户 home 目录的 .ipython/ 文件夹中\n",
    "#  配置是通过一个特殊文件， 当你启动 IPython， 就会默认加载这个存储在 profile_default 文件夹中的默认文件\n",
    "#  Linux 系统， 完整的 IPython 配置文件路径是：/home/wesm/.ipython/profile_default/ipython_config.py\n",
    "\n",
    "# 要启动这个文件， 运行下面的命令：\n",
    "# ipython profile create\n",
    "\n",
    "# 配置 Jupyter 有些不同， 因为你可以使用除了 Python 的其它语言\n",
    "#  要创建一个类似的 Jupyter 配置文件， 运行：\n",
    "# jupyter notebook --generate-config\n",
    "\n",
    "# 这样会在 home 目录的 .jupyter/jupyter_notebook_config.py 创建配置文件\n",
    "\n",
    "#  编辑完之后， 可以将它重命名\n",
    "# $ mv ~/.jupyter/jupyter_notebook_config.py ~/.jupyter/my_custom_config.py\n",
    "\n",
    "# 打开 Jupyter 之后，可以添加 --config 参数：\n",
    "# jupyter notebook --config=~/.jupyter/my_custom_config.py\n",
    "\n",
    "\n",
    "# B.6 总结\n",
    "# 建议持续学习 IPython 和 Jupyter，这两个项目的设计初衷就是提高生产率的， 可能还会发现一些工具，可以更便捷地使用 Python 和计算库\n",
    "\n",
    "# 可以在 nbviewer（https://nbviewer.jupyter.org/） 上找到更多有趣的 Jupyter notebooks"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
